using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using NHibernate;
using NHibernate.Context;
using NHibernate.Engine;
using NHibernate.Linq;
using MSP.Data.NHibernate.Repository;
using NHibernate.Criterion;

namespace MSP.Data.NHibernate
{
    public class Repository<T> : CurrentSessionContext, IIntKeyedRepository<T>, ILongKeyedRepository<T> where T : class
	{
		private ISession _session;
        private IStatelessSession _statelessSession;

		public Repository(ISession session)
		{
			_session = session;
		}

        public Repository(IStatelessSession statelessSession)
        {
            _statelessSession = statelessSession;
        }

		#region IRepository<T> Members

		public bool Add(T entity)
		{
			_session.Save(entity);
			return true;
		}

		public bool Add(IEnumerable<T> items)
		{
			foreach (T item in items)
			{
				_session.Save(item);
			}
			return true;
		}

		public bool Update(T entity)
		{
			_session.Update(entity);
			return true;
		}

        public bool SaveOrUpdateCopy(T entity)
        {
            _session.SaveOrUpdateCopy(entity);
            return true;
        }

        public bool SaveOrUpdateCopy(IEnumerable<T> items)
        {
            foreach (T item in items)
            {
                _session.Merge(item);
            }
            return true;
        }

        public bool Save(T entity)
        {
            EntityEntry entry = _session.GetSessionImplementation().PersistenceContext.GetEntry(entity);
            if (entry == null)
                Add(entity);
            else
                Update(entity);

            return true;
        }

        public bool Save(IEnumerable<T> items)
        {
            foreach (T item in items)
            {
                Save(item);
            }
            return true;
        }

        public bool Merge(T entity)
        {
            _session.Merge(entity);

            return true;
        }

		public bool Delete(T entity)
		{
			_session.Delete(entity);
			return true;
		}

		public bool Delete(IEnumerable<T> entities)
		{
            if (entities != null)
            {
                foreach (T entity in entities)
                {
                    _session.Delete(entity);
                }
            }
			return true;
		}

		#endregion

		#region IIntKeyedRepository<T> Members

		public T FindBy(int id)
		{
			return _session.Get<T>(id);
		}

		#endregion

        #region ILongKeyedRepository<T> Members

        public T FindBy(long id)
        {
            return _session.Get<T>(id);
        }

        #endregion

		#region IReadOnlyRepository<T> Members

		public IQueryable<T> All()
		{
			return _session.Query<T>();
		}

		public T FindBy(Expression<Func<T, bool>> expression)
		{
			return FilterBy(expression).Single();
		}

		public IQueryable<T> FilterBy(Expression<Func<T, bool>> expression)
		{
			return All().Where(expression).AsQueryable();
		}

		#endregion

		protected override ISession Session
		{
			get { return _session; }
			set { _session = value; }
        }

        #region Common repository functions

        protected ICriteria AddRangeAndOrder(ICriteria crit, int? firstRecord, int? numberOfRecords, SortedList<string, bool> sorting)
        {
            if (firstRecord != null)
                crit.SetFirstResult((int)firstRecord);
            if (numberOfRecords != null)
                crit.SetMaxResults((int)numberOfRecords);

            if (sorting != null)
            {
                foreach (string key in sorting.Keys)
                {
                    var asc = sorting[key];
                    crit.AddOrder(new Order(key, asc));
                }
            }
            return crit;
        }

        protected ICriteria AddDateRange(ICriteria crit, string propertyName, DateTime? dateFrom, DateTime? dateTo)
        {
            if (dateFrom != null)
                crit.Add(Restrictions.Ge(propertyName, (DateTime)dateFrom));
            if (dateTo != null)
                crit.Add(Restrictions.Le(propertyName, (DateTime)dateTo));
            return crit;
        }

        #endregion
    }
}